using System;
using System.Text;
using ch.ethz.ssh2.packets;
using ch.ethz.ssh2.sftp;
using ch.ethz.ssh2.util;
namespace ch.ethz.ssh2
{
	
	/// <summary> A <code>SFTPv3Client</code> represents a SFTP (protocol version 3)
	/// client connection tunnelled over a SSH-2 connection. This is a very simple
	/// (synchronous) implementation.
	/// <p>
	/// Basically, most methods in this class map directly to one of
	/// the packet types described in draft-ietf-secsh-filexfer-02.txt.
	/// <p>
	/// Note: this is experimental code.
	/// <p>
	/// Error handling: the methods of this class throw IOExceptions. However, unless
	/// there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will
	/// be thrown (a subclass of IOException). Therefore, you can implement more verbose
	/// behavior by checking if a thrown exception if of this type. If yes, then you
	/// can cast the exception and access detailed information about the failure. 
	/// <p>
	/// Notes about file names, directory names and paths, copy-pasted
	/// from the specs:
	/// <ul>
	/// <li>SFTP v3 represents file names as strings. File names are
	/// assumed to use the slash ('/') character as a directory separator.</li>
	/// <li>File names starting with a slash are "absolute", and are relative to
	/// the root of the file system.  Names starting with any other character
	/// are relative to the user's default directory (home directory).</li>
	/// <li>Servers SHOULD interpret a path name component ".." as referring to
	/// the parent directory, and "." as referring to the current directory.
	/// If the server implementation limits access to certain parts of the
	/// file system, it must be extra careful in parsing file names when
	/// enforcing such restrictions.  There have been numerous reported
	/// security bugs where a ".." in a path name has allowed access outside
	/// the intended area.</li>
	/// <li>An empty path name is valid, and it refers to the user's default
	/// directory (usually the user's home directory).</li>
	/// </ul>
	/// <p>
	/// If you are still not tired then please go on and read the comment for
	/// {@link #setCharset(String)}.
	/// 
	/// </summary>
	/// <author>  Christian Plattner
	/// </author>
	/// <version>  2.50, 03/15/10
	/// </version>
	public class SFTPv3Client
	{
		//UPGRADE_NOTE: Respective javadoc comments were merged.  It should be changed in order to comply with .NET documentation conventions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1199'"
		/// <summary> The currently used charset for filename encoding/decoding.
		/// 
		/// </summary>
		/// <seealso cref="setCharset(String)">
		/// 
		/// </seealso>
		/// <returns> The name of the charset (<code>null</code> if the platform's default charset is being used)
		/// </returns>
		/// <summary> Set the charset used to convert between Java Unicode Strings and byte encodings
		/// used by the server for paths and file names. Unfortunately, the SFTP v3 draft
		/// says NOTHING about such conversions (well, with the exception of error messages
		/// which have to be in UTF-8). Newer drafts specify to use UTF-8 for file names
		/// (if I remember correctly). However, a quick test using OpenSSH serving a EXT-3
		/// filesystem has shown that UTF-8 seems to be a bad choice for SFTP v3 (tested with
		/// filenames containing german umlauts). "windows-1252" seems to work better for Europe.
		/// Luckily, "windows-1252" is the platform default in my case =).
		/// <p>
		/// If you don't set anything, then the platform default will be used (this is the default
		/// behavior).
		/// 
		/// </summary>
		/// <seealso cref="getCharset()">
		/// 
		/// </seealso>
		/// <param name="charset">the name of the charset to be used or <code>null</code> to use the platform's
		/// default encoding.
		/// </param>
		/// <throws>  IOException </throws>
		virtual public System.String Charset
		{
			get
			{
				return charsetName;
			}
			
			set
			{
				if (value == null)
				{
					charsetName = value;
					return ;
				}
				
				try
				{
                    System.Text.Encoding.GetEncoding(value);
				}
				catch (System.Exception e)
				{
					throw (System.IO.IOException) new System.IO.IOException("This charset is not supported",e);
				}
				charsetName = value;
			}
			
		}
		/// <summary> Returns the negotiated SFTP protocol version between the client and the server.
		/// 
		/// </summary>
		/// <returns> SFTP protocol version, i.e., "3".
		/// 
		/// </returns>
		virtual public int ProtocolVersion
		{
			get
			{
				return protocol_version;
			}
			
		}
		//UPGRADE_NOTE: Final was removed from the declaration of 'conn '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
		internal Connection conn;
		//UPGRADE_NOTE: Final was removed from the declaration of 'sess '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
		internal Session sess;
		//UPGRADE_NOTE: Final was removed from the declaration of 'debug '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
		//UPGRADE_ISSUE: Class hierarchy differences between 'java.io.PrintStream' and 'System.IO.StreamWriter' may cause compilation errors. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1186'"
		internal System.IO.StreamWriter debug;
		
		internal bool flag_closed = false;
		
		internal System.IO.Stream input;
		internal System.IO.Stream output;
		
		internal int protocol_version = 0;
		//UPGRADE_TODO: Class 'java.util.HashMap' was converted to 'System.Collections.Hashtable' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilHashMap'"
		internal System.Collections.Hashtable server_extensions = new System.Collections.Hashtable();
		
		internal int next_request_id = 1000;
		
		internal System.String charsetName = null;
		
		/// <summary> Create a SFTP v3 client.
		/// 
		/// </summary>
		/// <param name="conn">The underlying SSH-2 connection to be used.
		/// </param>
		/// <param name="debug">
		/// </param>
		/// <throws>  IOException </throws>
		/// <summary> 
		/// </summary>
		/// <deprecated> this constructor (debug version) will disappear in the future,
		/// use {@link #SFTPv3Client(Connection)} instead.
		/// </deprecated>
		//UPGRADE_ISSUE: Class hierarchy differences between 'java.io.PrintStream' and 'System.IO.StreamWriter' may cause compilation errors. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1186'"
		public SFTPv3Client(Connection conn, System.IO.StreamWriter debug)
		{
			if (conn == null)
				throw new System.ArgumentException("Cannot accept null argument!");
			
			this.conn = conn;
			this.debug = debug;
			
			if (debug != null)
				debug.WriteLine("Opening session and starting SFTP subsystem.");
			
			sess = conn.openSession();
			sess.startSubSystem("sftp");
			
			input = sess.Stdout;
			output = new System.IO.BufferedStream(sess.Stdin, 2048);
			
			if ((input == null) || (output == null))
				throw new System.IO.IOException("There is a problem with the streams of the underlying channel.");
			
			init();
		}
		
		/// <summary> Create a SFTP v3 client.
		/// 
		/// </summary>
		/// <param name="conn">The underlying SSH-2 connection to be used.
		/// </param>
		/// <throws>  IOException </throws>
		public SFTPv3Client(Connection conn):this(conn, null)
		{
		}
		
		private void  checkHandleValidAndOpen(SFTPv3FileHandle handle)
		{
			if (handle.client != this)
				throw new System.IO.IOException("The file handle was created with another SFTPv3FileHandle instance.");
			
			if (handle.isClosed == true)
				throw new System.IO.IOException("The file handle is closed.");
		}
		
		private void  sendMessage(int type, int requestId, byte[] msg, int off, int len)
		{
			int msglen = len + 1;
			
			if (type != Packet.SSH_FXP_INIT)
				msglen += 4;
			
			output.WriteByte((System.Byte) (msglen >> 24));
			output.WriteByte((System.Byte) (msglen >> 16));
			output.WriteByte((System.Byte) (msglen >> 8));
			output.WriteByte((System.Byte) msglen);
			output.WriteByte((System.Byte) type);
			
			if (type != Packet.SSH_FXP_INIT)
			{
				output.WriteByte((System.Byte) (requestId >> 24));
				output.WriteByte((System.Byte) (requestId >> 16));
				output.WriteByte((System.Byte) (requestId >> 8));
				output.WriteByte((System.Byte) requestId);
			}
			
			output.Write(msg, off, len);
			output.Flush();
		}
		
		private void  sendMessage(int type, int requestId, byte[] msg)
		{
			sendMessage(type, requestId, msg, 0, msg.Length);
		}
		
		private void  readBytes(byte[] buff, int pos, int len)
		{
			while (len > 0)
			{
				int count = (input is ch.ethz.ssh2.StreamGobbler || input is ch.ethz.ssh2.channel.ChannelInputStream)?(int) Utility.InvokeMethodAsVirtual(input, "read", new System.Object[]{buff, pos, len}):Utility.ReadInput(input, buff, pos, len);
				if (count < 0)
					throw new System.IO.IOException("Unexpected end of sftp stream.");
				if ((count == 0) || (count > len))
					throw new System.IO.IOException("Underlying stream implementation is bogus!");
				len -= count;
				pos += count;
			}
		}
		
		/// <summary> Read a message and guarantee that the <b>contents</b> is not larger than
		/// <code>maxlen</code> bytes.
		/// <p>
		/// Note: receiveMessage(34000) actually means that the message may be up to 34004
		/// bytes (the length attribute preceeding the contents is 4 bytes).
		/// 
		/// </summary>
		/// <param name="maxlen">
		/// </param>
		/// <returns> the message contents
		/// </returns>
		/// <throws>  IOException </throws>
		private byte[] receiveMessage(int maxlen)
		{
			byte[] msglen = new byte[4];
			
			readBytes(msglen, 0, 4);
			
			int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff));
			
			if ((len > maxlen) || (len <= 0))
				throw new System.IO.IOException("Illegal sftp packet len: " + len);
			
			byte[] msg = new byte[len];
			
			readBytes(msg, 0, len);
			
			return msg;
		}
		
		private int generateNextRequestID()
		{
			lock (this)
			{
				return next_request_id++;
			}
		}
		
		private void  closeHandle(byte[] handle)
		{
			int req_id = generateNextRequestID();
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(handle, 0, handle.Length);
			
			sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes());
			
			expectStatusOKMessage(req_id);
		}
		
		private SFTPv3FileAttributes readAttrs(TypesReader tr)
		{
			/*
			* uint32   flags
			* uint64   size           present only if flag SSH_FILEXFER_ATTR_SIZE
			* uint32   uid            present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
			* uint32   gid            present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
			* uint32   permissions    present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
			* uint32   atime          present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
			* uint32   mtime          present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
			* uint32   extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
			* string   extended_type
			* string   extended_data
			* ...      more extended data (extended_type - extended_data pairs),
			*          so that number of pairs equals extended_count
			*/
			
			SFTPv3FileAttributes fa = new SFTPv3FileAttributes();
			
			int flags = tr.readUINT32();
			
			if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0)
			{
				if (debug != null)
					debug.WriteLine("SSH_FILEXFER_ATTR_SIZE");
				fa.size = (long) tr.readUINT64();
			}
			
			if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0)
			{
				if (debug != null)
					debug.WriteLine("SSH_FILEXFER_ATTR_V3_UIDGID");
				fa.uid = (System.Int32) tr.readUINT32();
				fa.gid = (System.Int32) tr.readUINT32();
			}
			
			if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0)
			{
				if (debug != null)
					debug.WriteLine("SSH_FILEXFER_ATTR_PERMISSIONS");
				fa.permissions = (System.Int32) tr.readUINT32();
			}
			
			if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0)
			{
				if (debug != null)
					debug.WriteLine("SSH_FILEXFER_ATTR_V3_ACMODTIME");
				fa.atime = (System.Int32) tr.readUINT32();
				fa.mtime = (System.Int32) tr.readUINT32();
			}
			
			if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0)
			{
				int count = tr.readUINT32();
				
				if (debug != null)
					debug.WriteLine("SSH_FILEXFER_ATTR_EXTENDED (" + count + ")");
				
				/* Read it anyway to detect corrupt packets */
				
				while (count > 0)
				{
					tr.readByteString();
					tr.readByteString();
					count--;
				}
			}
			
			return fa;
		}
		
		/// <summary> Retrieve the file attributes of an open file.
		/// 
		/// </summary>
		/// <param name="handle">a SFTPv3FileHandle handle.
		/// </param>
		/// <returns> a SFTPv3FileAttributes object.
		/// </returns>
		/// <throws>  IOException </throws>
		public virtual SFTPv3FileAttributes fstat(SFTPv3FileHandle handle)
		{
			checkHandleValidAndOpen(handle);
			
			int req_id = generateNextRequestID();
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(handle.fileHandle, 0, handle.fileHandle.Length);
			
			if (debug != null)
			{
				debug.WriteLine("Sending SSH_FXP_FSTAT...");
				debug.Flush();
			}
			
			sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes());
			
			byte[] resp = receiveMessage(34000);
			
			if (debug != null)
			{
				debug.WriteLine("Got REPLY.");
				debug.Flush();
			}
			
			TypesReader tr = new TypesReader(resp);
			
			int t = tr.readByte();
			
			int rep_id = tr.readUINT32();
			if (rep_id != req_id)
				throw new System.IO.IOException("The server sent an invalid id field.");
			
			if (t == Packet.SSH_FXP_ATTRS)
			{
				return readAttrs(tr);
			}
			
			if (t != Packet.SSH_FXP_STATUS)
				throw new System.IO.IOException("The SFTP server sent an unexpected packet type (" + t + ")");
			
			int errorCode = tr.readUINT32();
			
			throw new SFTPException(tr.readString(), errorCode);
		}
		
		private SFTPv3FileAttributes statBoth(System.String path, int statMethod)
		{
			int req_id = generateNextRequestID();
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(path, charsetName);
			
			if (debug != null)
			{
				debug.WriteLine("Sending SSH_FXP_STAT/SSH_FXP_LSTAT...");
				debug.Flush();
			}
			
			sendMessage(statMethod, req_id, tw.getBytes());
			
			byte[] resp = receiveMessage(34000);
			
			if (debug != null)
			{
				debug.WriteLine("Got REPLY.");
				debug.Flush();
			}
			
			TypesReader tr = new TypesReader(resp);
			
			int t = tr.readByte();
			
			int rep_id = tr.readUINT32();
			if (rep_id != req_id)
				throw new System.IO.IOException("The server sent an invalid id field.");
			
			if (t == Packet.SSH_FXP_ATTRS)
			{
				return readAttrs(tr);
			}
			
			if (t != Packet.SSH_FXP_STATUS)
				throw new System.IO.IOException("The SFTP server sent an unexpected packet type (" + t + ")");
			
			int errorCode = tr.readUINT32();
			
			throw new SFTPException(tr.readString(), errorCode);
		}
		
		/// <summary> Retrieve the file attributes of a file. This method
		/// follows symbolic links on the server.
		/// 
		/// </summary>
		/// <seealso cref="lstat(String)">
		/// 
		/// </seealso>
		/// <param name="path">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <returns> a SFTPv3FileAttributes object.
		/// </returns>
		/// <throws>  IOException </throws>
		public virtual SFTPv3FileAttributes stat(System.String path)
		{
			return statBoth(path, Packet.SSH_FXP_STAT);
		}
		
		/// <summary> Retrieve the file attributes of a file. This method
		/// does NOT follow symbolic links on the server.
		/// 
		/// </summary>
		/// <seealso cref="stat(String)">
		/// 
		/// </seealso>
		/// <param name="path">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <returns> a SFTPv3FileAttributes object.
		/// </returns>
		/// <throws>  IOException </throws>
		public virtual SFTPv3FileAttributes lstat(System.String path)
		{
			return statBoth(path, Packet.SSH_FXP_LSTAT);
		}
		
		/// <summary> Read the target of a symbolic link.
		/// 
		/// </summary>
		/// <param name="path">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <returns> The target of the link.
		/// </returns>
		/// <throws>  IOException </throws>
		public virtual System.String readLink(System.String path)
		{
			int req_id = generateNextRequestID();
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(path, charsetName);
			
			if (debug != null)
			{
				debug.WriteLine("Sending SSH_FXP_READLINK...");
				debug.Flush();
			}
			
			sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes());
			
			byte[] resp = receiveMessage(34000);
			
			if (debug != null)
			{
				debug.WriteLine("Got REPLY.");
				debug.Flush();
			}
			
			TypesReader tr = new TypesReader(resp);
			
			int t = tr.readByte();
			
			int rep_id = tr.readUINT32();
			if (rep_id != req_id)
				throw new System.IO.IOException("The server sent an invalid id field.");
			
			if (t == Packet.SSH_FXP_NAME)
			{
				int count = tr.readUINT32();
				
				if (count != 1)
					throw new System.IO.IOException("The server sent an invalid SSH_FXP_NAME packet.");
				
				return tr.readString(charsetName);
			}
			
			if (t != Packet.SSH_FXP_STATUS)
				throw new System.IO.IOException("The SFTP server sent an unexpected packet type (" + t + ")");
			
			int errorCode = tr.readUINT32();
			
			throw new SFTPException(tr.readString(), errorCode);
		}
		
		private void  expectStatusOKMessage(int id)
		{
			byte[] resp = receiveMessage(34000);
			
			if (debug != null)
			{
				debug.WriteLine("Got REPLY.");
				debug.Flush();
			}
			
			TypesReader tr = new TypesReader(resp);
			
			int t = tr.readByte();
			
			int rep_id = tr.readUINT32();
			if (rep_id != id)
				throw new System.IO.IOException("The server sent an invalid id field.");
			
			if (t != Packet.SSH_FXP_STATUS)
				throw new System.IO.IOException("The SFTP server sent an unexpected packet type (" + t + ")");
			
			int errorCode = tr.readUINT32();
			
			if (errorCode == ErrorCodes.SSH_FX_OK)
				return ;
			
			throw new SFTPException(tr.readString(), errorCode);
		}
		
		/// <summary>  Modify the attributes of a file. Used for operations such as changing
		/// the ownership, permissions or access times, as well as for truncating a file.
		/// 
		/// </summary>
		/// <param name="path">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <param name="attr">A SFTPv3FileAttributes object. Specifies the modifications to be
		/// made to the attributes of the file. Empty fields will be ignored.
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  setstat(System.String path, SFTPv3FileAttributes attr)
		{
			int req_id = generateNextRequestID();
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(path, charsetName);
			tw.writeBytes(createAttrs(attr));
			
			if (debug != null)
			{
				debug.WriteLine("Sending SSH_FXP_SETSTAT...");
				debug.Flush();
			}
			
			sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes());
			
			expectStatusOKMessage(req_id);
		}
		
		/// <summary> 	Modify the attributes of a file. Used for operations such as changing
		/// the ownership, permissions or access times, as well as for truncating a file.
		/// 
		/// </summary>
		/// <param name="handle">a SFTPv3FileHandle handle
		/// </param>
		/// <param name="attr">A SFTPv3FileAttributes object. Specifies the modifications to be
		/// made to the attributes of the file. Empty fields will be ignored.
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  fsetstat(SFTPv3FileHandle handle, SFTPv3FileAttributes attr)
		{
			checkHandleValidAndOpen(handle);
			
			int req_id = generateNextRequestID();
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(handle.fileHandle, 0, handle.fileHandle.Length);
			tw.writeBytes(createAttrs(attr));
			
			if (debug != null)
			{
				debug.WriteLine("Sending SSH_FXP_FSETSTAT...");
				debug.Flush();
			}
			
			sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes());
			
			expectStatusOKMessage(req_id);
		}
		
		/// <summary> Create a symbolic link on the server. Creates a link "src" that points
		/// to "target".
		/// 
		/// </summary>
		/// <param name="src">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <param name="target">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  createSymlink(System.String src, System.String target)
		{
			int req_id = generateNextRequestID();
			
			/* Either I am too stupid to understand the SFTP draft
			* or the OpenSSH guys changed the semantics of src and target.
			*/
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(target, charsetName);
			tw.writeString(src, charsetName);
			
			if (debug != null)
			{
				debug.WriteLine("Sending SSH_FXP_SYMLINK...");
				debug.Flush();
			}
			
			sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes());
			
			expectStatusOKMessage(req_id);
		}
		
		/// <summary> Have the server canonicalize any given path name to an absolute path.
		/// This is useful for converting path names containing ".." components or
		/// relative pathnames without a leading slash into absolute paths.
		/// 
		/// </summary>
		/// <param name="path">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <returns> An absolute path.
		/// </returns>
		/// <throws>  IOException </throws>
		public virtual System.String canonicalPath(System.String path)
		{
			int req_id = generateNextRequestID();
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(path, charsetName);
			
			if (debug != null)
			{
				debug.WriteLine("Sending SSH_FXP_REALPATH...");
				debug.Flush();
			}
			
			sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes());
			
			byte[] resp = receiveMessage(34000);
			
			if (debug != null)
			{
				debug.WriteLine("Got REPLY.");
				debug.Flush();
			}
			
			TypesReader tr = new TypesReader(resp);
			
			int t = tr.readByte();
			
			int rep_id = tr.readUINT32();
			if (rep_id != req_id)
				throw new System.IO.IOException("The server sent an invalid id field.");
			
			if (t == Packet.SSH_FXP_NAME)
			{
				int count = tr.readUINT32();
				
				if (count != 1)
					throw new System.IO.IOException("The server sent an invalid SSH_FXP_NAME packet.");
				
				return tr.readString(charsetName);
			}
			
			if (t != Packet.SSH_FXP_STATUS)
				throw new System.IO.IOException("The SFTP server sent an unexpected packet type (" + t + ")");
			
			int errorCode = tr.readUINT32();
			
			throw new SFTPException(tr.readString(), errorCode);
		}
		
		private System.Collections.ArrayList scanDirectory(byte[] handle)
		{
			System.Collections.ArrayList files = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));
			
			while (true)
			{
				int req_id = generateNextRequestID();
				
				TypesWriter tw = new TypesWriter();
				tw.writeString(handle, 0, handle.Length);
				
				if (debug != null)
				{
					debug.WriteLine("Sending SSH_FXP_READDIR...");
					debug.Flush();
				}
				
				sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes());
				
				byte[] resp = receiveMessage(34000);
				
				if (debug != null)
				{
					debug.WriteLine("Got REPLY.");
					debug.Flush();
				}
				
				TypesReader tr = new TypesReader(resp);
				
				int t = tr.readByte();
				
				int rep_id = tr.readUINT32();
				if (rep_id != req_id)
					throw new System.IO.IOException("The server sent an invalid id field.");
				
				if (t == Packet.SSH_FXP_NAME)
				{
					int count = tr.readUINT32();
					
					if (debug != null)
						debug.WriteLine("Parsing " + count + " name entries...");
					
					while (count > 0)
					{
						SFTPv3DirectoryEntry dirEnt = new SFTPv3DirectoryEntry();
						
						dirEnt.filename = tr.readString(charsetName);
						dirEnt.longEntry = tr.readString(charsetName);
						
						dirEnt.attributes = readAttrs(tr);
						files.Add(dirEnt);
						
						if (debug != null)
							debug.WriteLine("File: '" + dirEnt.filename + "'");
						count--;
					}
					continue;
				}
				
				if (t != Packet.SSH_FXP_STATUS)
					throw new System.IO.IOException("The SFTP server sent an unexpected packet type (" + t + ")");
				
				int errorCode = tr.readUINT32();
				
				if (errorCode == ErrorCodes.SSH_FX_EOF)
					return files;
				
				throw new SFTPException(tr.readString(), errorCode);
			}
		}
		
		private byte[] openDirectory(System.String path)
		{
			int req_id = generateNextRequestID();
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(path, charsetName);
			
			if (debug != null)
			{
				debug.WriteLine("Sending SSH_FXP_OPENDIR...");
				debug.Flush();
			}
			
			sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes());
			
			byte[] resp = receiveMessage(34000);
			
			TypesReader tr = new TypesReader(resp);
			
			int t = tr.readByte();
			
			int rep_id = tr.readUINT32();
			if (rep_id != req_id)
				throw new System.IO.IOException("The server sent an invalid id field.");
			
			if (t == Packet.SSH_FXP_HANDLE)
			{
				if (debug != null)
				{
					debug.WriteLine("Got SSH_FXP_HANDLE.");
					debug.Flush();
				}
				
				byte[] handle = tr.readByteString();
				return handle;
			}
			
			if (t != Packet.SSH_FXP_STATUS)
				throw new System.IO.IOException("The SFTP server sent an unexpected packet type (" + t + ")");
			
			int errorCode = tr.readUINT32();
			System.String errorMessage = tr.readString();
			
			throw new SFTPException(errorMessage, errorCode);
		}
		
		private System.String expandString(byte[] b, int off, int len)
		{
			System.Text.StringBuilder sb = new System.Text.StringBuilder();
			
			for (int i = 0; i < len; i++)
			{
				int c = b[off + i] & 0xff;
				
				if ((c >= 32) && (c <= 126))
				{
					sb.Append((char) c);
				}
				else
				{
					sb.Append("{0x" + System.Convert.ToString(c, 16) + "}");
				}
			}
			
			return sb.ToString();
		}
		
		private void  init()
		{
			/* Send SSH_FXP_INIT (version 3) */
			
			//UPGRADE_NOTE: Final was removed from the declaration of 'client_version '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
			int client_version = 3;
			
			if (debug != null)
				debug.WriteLine("Sending SSH_FXP_INIT (" + client_version + ")...");
			
			TypesWriter tw = new TypesWriter();
			tw.writeUINT32(client_version);
			sendMessage(Packet.SSH_FXP_INIT, 0, tw.getBytes());
			
			/* Receive SSH_FXP_VERSION */
			
			if (debug != null)
				debug.WriteLine("Waiting for SSH_FXP_VERSION...");
			
			TypesReader tr = new TypesReader(receiveMessage(34000)); /* Should be enough for any reasonable server */
			
			int type = tr.readByte();
			
			if (type != Packet.SSH_FXP_VERSION)
			{
				throw new System.IO.IOException("The server did not send a SSH_FXP_VERSION packet (got " + type + ")");
			}
			
			protocol_version = tr.readUINT32();
			
			if (debug != null)
				debug.WriteLine("SSH_FXP_VERSION: protocol_version = " + protocol_version);
			
			if (protocol_version != 3)
				throw new System.IO.IOException("Server version " + protocol_version + " is currently not supported");
			
			/* Read and save extensions (if any) for later use */
			
			while (tr.remain() != 0)
			{
				System.String name = tr.readString();
				byte[] values = tr.readByteString();
				server_extensions[name] = values;
				
				if (debug != null)
					debug.WriteLine("SSH_FXP_VERSION: extension: " + name + " = '" + expandString(values, 0, values.Length) + "'");
			}
		}
		
		/// <summary> Close this SFTP session. NEVER forget to call this method to free up
		/// resources - even if you got an exception from one of the other methods.
		/// Sometimes these other methods may throw an exception, saying that the
		/// underlying channel is closed (this can happen, e.g., if the other server
		/// sent a close message.) However, as long as you have not called the
		/// <code>close()</code> method, you are likely wasting resources.
		/// 
		/// </summary>
		public virtual void  close()
		{
			sess.close();
		}
		
		/// <summary> List the contents of a directory.
		/// 
		/// </summary>
		/// <param name="dirName">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <returns> A Vector containing {@link SFTPv3DirectoryEntry} objects.
		/// </returns>
		/// <throws>  IOException </throws>
		public virtual System.Collections.ArrayList ls(System.String dirName)
		{
			byte[] handle = openDirectory(dirName);
			System.Collections.ArrayList result = scanDirectory(handle);
			closeHandle(handle);
			return result;
		}
		
		/// <summary> Create a new directory.
		/// 
		/// </summary>
		/// <param name="dirName">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <param name="posixPermissions">the permissions for this directory, e.g., "0700" (remember that
		/// this is octal noation). The server will likely apply a umask.
		/// 
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  mkdir(System.String dirName, int posixPermissions)
		{
			int req_id = generateNextRequestID();
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(dirName, charsetName);
			tw.writeUINT32(AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS);
			tw.writeUINT32(posixPermissions);
			
			sendMessage(Packet.SSH_FXP_MKDIR, req_id, tw.getBytes());
			
			expectStatusOKMessage(req_id);
		}
		
		/// <summary> Remove a file.
		/// 
		/// </summary>
		/// <param name="fileName">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  rm(System.String fileName)
		{
			int req_id = generateNextRequestID();
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(fileName, charsetName);
			
			sendMessage(Packet.SSH_FXP_REMOVE, req_id, tw.getBytes());
			
			expectStatusOKMessage(req_id);
		}
		
		/// <summary> Remove an empty directory. 
		/// 
		/// </summary>
		/// <param name="dirName">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  rmdir(System.String dirName)
		{
			int req_id = generateNextRequestID();
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(dirName, charsetName);
			
			sendMessage(Packet.SSH_FXP_RMDIR, req_id, tw.getBytes());
			
			expectStatusOKMessage(req_id);
		}
		
		/// <summary> Move a file or directory.
		/// 
		/// </summary>
		/// <param name="oldPath">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <param name="newPath">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  mv(System.String oldPath, System.String newPath)
		{
			int req_id = generateNextRequestID();
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(oldPath, charsetName);
			tw.writeString(newPath, charsetName);
			
			sendMessage(Packet.SSH_FXP_RENAME, req_id, tw.getBytes());
			
			expectStatusOKMessage(req_id);
		}
		
		/// <summary> Open a file for reading.
		/// 
		/// </summary>
		/// <param name="fileName">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <returns> a SFTPv3FileHandle handle
		/// </returns>
		/// <throws>  IOException </throws>
		public virtual SFTPv3FileHandle openFileRO(System.String fileName)
		{
			return openFile(fileName, 0x00000001, null); // SSH_FXF_READ	
		}
		
		/// <summary> Open a file for reading and writing.
		/// 
		/// </summary>
		/// <param name="fileName">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <returns> a SFTPv3FileHandle handle
		/// </returns>
		/// <throws>  IOException </throws>
		public virtual SFTPv3FileHandle openFileRW(System.String fileName)
		{
			return openFile(fileName, 0x00000003, null); // SSH_FXF_READ | SSH_FXF_WRITE
		}
		
		// Append is broken (already in the specification, because there is no way to
		// send a write operation (what offset to use??))
		//	public SFTPv3FileHandle openFileRWAppend(String fileName) throws IOException
		//	{
		//		return openFile(fileName, 0x00000007, null); // SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND
		//	}
		
		/// <summary> Create a file and open it for reading and writing.
		/// Same as {@link #createFile(String, SFTPv3FileAttributes) createFile(fileName, null)}.
		/// 
		/// </summary>
		/// <param name="fileName">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <returns> a SFTPv3FileHandle handle
		/// </returns>
		/// <throws>  IOException </throws>
		public virtual SFTPv3FileHandle createFile(System.String fileName)
		{
			return createFile(fileName, null);
		}
		
		/// <summary> Create a file and open it for reading and writing.
		/// You can specify the default attributes of the file (the server may or may
		/// not respect your wishes).
		/// 
		/// </summary>
		/// <param name="fileName">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <param name="attr">may be <code>null</code> to use server defaults. Probably only
		/// the <code>uid</code>, <code>gid</code> and <code>permissions</code>
		/// (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
		/// structure make sense. You need only to set those fields where you want
		/// to override the server's defaults.
		/// </param>
		/// <returns> a SFTPv3FileHandle handle
		/// </returns>
		/// <throws>  IOException </throws>
		public virtual SFTPv3FileHandle createFile(System.String fileName, SFTPv3FileAttributes attr)
		{
			return openFile(fileName, 0x00000008 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE
		}
		
		/// <summary> Create a file (truncate it if it already exists) and open it for reading and writing.
		/// Same as {@link #createFileTruncate(String, SFTPv3FileAttributes) createFileTruncate(fileName, null)}.
		/// 
		/// </summary>
		/// <param name="fileName">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <returns> a SFTPv3FileHandle handle
		/// </returns>
		/// <throws>  IOException </throws>
		public virtual SFTPv3FileHandle createFileTruncate(System.String fileName)
		{
			return createFileTruncate(fileName, null);
		}
		
		/// <summary> reate a file (truncate it if it already exists) and open it for reading and writing.
		/// You can specify the default attributes of the file (the server may or may
		/// not respect your wishes).
		/// 
		/// </summary>
		/// <param name="fileName">See the {@link SFTPv3Client comment} for the class for more details.
		/// </param>
		/// <param name="attr">may be <code>null</code> to use server defaults. Probably only
		/// the <code>uid</code>, <code>gid</code> and <code>permissions</code>
		/// (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
		/// structure make sense. You need only to set those fields where you want
		/// to override the server's defaults.
		/// </param>
		/// <returns> a SFTPv3FileHandle handle
		/// </returns>
		/// <throws>  IOException </throws>
		public virtual SFTPv3FileHandle createFileTruncate(System.String fileName, SFTPv3FileAttributes attr)
		{
			return openFile(fileName, 0x00000018 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_READ | SSH_FXF_WRITE
		}
		
		private byte[] createAttrs(SFTPv3FileAttributes attr)
		{
			TypesWriter tw = new TypesWriter();
			
			int attrFlags = 0;
			
			if (attr == null)
			{
				tw.writeUINT32(0);
			}
			else
			{
				if (attr.size != null)
					attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE;

				if ((attr.uid != null) && (attr.gid != null))
					attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID;
				
				if (attr.permissions != null)
					attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS;
				
				if ((attr.atime != null) && (attr.mtime != null))
					attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME;
				
				tw.writeUINT32(attrFlags);
				
				if (attr.size != null)
					tw.writeUINT64((long) attr.size);
				
				if ((attr.uid != null) && (attr.gid != null))
				{
					tw.writeUINT32((int)attr.uid);
                    tw.writeUINT32((int)attr.gid);
				}
				
				if (attr.permissions != null)
                    tw.writeUINT32((int)attr.permissions);
				
				if ((attr.atime != null) && (attr.mtime != null))
				{
                    tw.writeUINT32((int)attr.atime);
                    tw.writeUINT32((int)attr.mtime);
				}
			}
			
			return tw.getBytes();
		}
		
		private SFTPv3FileHandle openFile(System.String fileName, int flags, SFTPv3FileAttributes attr)
		{
			int req_id = generateNextRequestID();
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(fileName, charsetName);
			tw.writeUINT32(flags);
			tw.writeBytes(createAttrs(attr));
			
			if (debug != null)
			{
				debug.WriteLine("Sending SSH_FXP_OPEN...");
				debug.Flush();
			}
			
			sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes());
			
			byte[] resp = receiveMessage(34000);
			
			TypesReader tr = new TypesReader(resp);
			
			int t = tr.readByte();
			
			int rep_id = tr.readUINT32();
			if (rep_id != req_id)
				throw new System.IO.IOException("The server sent an invalid id field.");
			
			if (t == Packet.SSH_FXP_HANDLE)
			{
				if (debug != null)
				{
					debug.WriteLine("Got SSH_FXP_HANDLE.");
					debug.Flush();
				}
				
				return new SFTPv3FileHandle(this, tr.readByteString());
			}
			
			if (t != Packet.SSH_FXP_STATUS)
				throw new System.IO.IOException("The SFTP server sent an unexpected packet type (" + t + ")");
			
			int errorCode = tr.readUINT32();
			System.String errorMessage = tr.readString();
			
			throw new SFTPException(errorMessage, errorCode);
		}
		
		/// <summary> Read bytes from a file. No more than 32768 bytes may be read at once.
		/// Be aware that the semantics of read() are different than for Java streams.
		/// <p>
		/// <ul>
		/// <li>The server will read as many bytes as it can from the file (up to <code>len</code>),
		/// and return them.</li>
		/// <li>If EOF is encountered before reading any data, <code>-1</code> is returned.
		/// <li>If an error occurs, an exception is thrown</li>.
		/// <li>For normal disk files, it is guaranteed that the server will return the specified
		/// number of bytes, or up to end of file. For, e.g., device files this may return
		/// fewer bytes than requested.</li>
		/// </ul>
		/// 
		/// </summary>
		/// <param name="handle">a SFTPv3FileHandle handle
		/// </param>
		/// <param name="fileOffset">offset (in bytes) in the file
		/// </param>
		/// <param name="dst">the destination byte array
		/// </param>
		/// <param name="dstoff">offset in the destination byte array
		/// </param>
		/// <param name="len">how many bytes to read, 0 &lt; len &lt;= 32768 bytes
		/// </param>
		/// <returns> the number of bytes that could be read, may be less than requested if
		/// the end of the file is reached, -1 is returned in case of <code>EOF</code>
		/// </returns>
		/// <throws>  IOException </throws>
		public virtual int read(SFTPv3FileHandle handle, long fileOffset, byte[] dst, int dstoff, int len)
		{
			checkHandleValidAndOpen(handle);
			
			if ((len > 32768) || (len <= 0))
				throw new System.ArgumentException("invalid len argument");
			
			int req_id = generateNextRequestID();
			
			TypesWriter tw = new TypesWriter();
			tw.writeString(handle.fileHandle, 0, handle.fileHandle.Length);
			tw.writeUINT64(fileOffset);
			tw.writeUINT32(len);
			
			if (debug != null)
			{
				debug.WriteLine("Sending SSH_FXP_READ...");
				debug.Flush();
			}
			
			sendMessage(Packet.SSH_FXP_READ, req_id, tw.getBytes());
			
			byte[] resp = receiveMessage(34000);
			
			TypesReader tr = new TypesReader(resp);
			
			int t = tr.readByte();
			
			int rep_id = tr.readUINT32();
			if (rep_id != req_id)
				throw new System.IO.IOException("The server sent an invalid id field.");
			
			if (t == Packet.SSH_FXP_DATA)
			{
				if (debug != null)
				{
					debug.WriteLine("Got SSH_FXP_DATA...");
					debug.Flush();
				}
				
				int readLen = tr.readUINT32();
				
				if ((readLen < 0) || (readLen > len))
					throw new System.IO.IOException("The server sent an invalid length field.");
				
				tr.readBytes(dst, dstoff, readLen);
				
				return readLen;
			}
			
			if (t != Packet.SSH_FXP_STATUS)
				throw new System.IO.IOException("The SFTP server sent an unexpected packet type (" + t + ")");
			
			int errorCode = tr.readUINT32();
			
			if (errorCode == ErrorCodes.SSH_FX_EOF)
			{
				if (debug != null)
				{
					debug.WriteLine("Got SSH_FX_EOF.");
					debug.Flush();
				}
				
				return - 1;
			}
			
			System.String errorMessage = tr.readString();
			
			throw new SFTPException(errorMessage, errorCode);
		}
		
		/// <summary> Write bytes to a file. If <code>len</code> &gt; 32768, then the write operation will
		/// be split into multiple writes.
		/// 
		/// </summary>
		/// <param name="handle">a SFTPv3FileHandle handle.
		/// </param>
		/// <param name="fileOffset">offset (in bytes) in the file.
		/// </param>
		/// <param name="src">the source byte array.
		/// </param>
		/// <param name="srcoff">offset in the source byte array.
		/// </param>
		/// <param name="len">how many bytes to write.
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  write(SFTPv3FileHandle handle, long fileOffset, byte[] src, int srcoff, int len)
		{
			checkHandleValidAndOpen(handle);
			
			while (len > 0)
			{
				int writeRequestLen = len;
				
				if (writeRequestLen > 32768)
					writeRequestLen = 32768;
				
				int req_id = generateNextRequestID();
				
				TypesWriter tw = new TypesWriter();
				tw.writeString(handle.fileHandle, 0, handle.fileHandle.Length);
				tw.writeUINT64(fileOffset);
				tw.writeString(src, srcoff, writeRequestLen);
				
				if (debug != null)
				{
					debug.WriteLine("Sending SSH_FXP_WRITE...");
					debug.Flush();
				}
				
				sendMessage(Packet.SSH_FXP_WRITE, req_id, tw.getBytes());
				
				fileOffset += writeRequestLen;
				
				srcoff += writeRequestLen;
				len -= writeRequestLen;
				
				byte[] resp = receiveMessage(34000);
				
				TypesReader tr = new TypesReader(resp);
				
				int t = tr.readByte();
				
				int rep_id = tr.readUINT32();
				if (rep_id != req_id)
					throw new System.IO.IOException("The server sent an invalid id field.");
				
				if (t != Packet.SSH_FXP_STATUS)
					throw new System.IO.IOException("The SFTP server sent an unexpected packet type (" + t + ")");
				
				int errorCode = tr.readUINT32();
				
				if (errorCode == ErrorCodes.SSH_FX_OK)
					continue;
				
				System.String errorMessage = tr.readString();
				
				throw new SFTPException(errorMessage, errorCode);
			}
		}
		
		/// <summary> Close a file.
		/// 
		/// </summary>
		/// <param name="handle">a SFTPv3FileHandle handle
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  closeFile(SFTPv3FileHandle handle)
		{
			if (handle == null)
				throw new System.ArgumentException("the handle argument may not be null");
			
			try
			{
				if (handle.isClosed == false)
				{
					closeHandle(handle.fileHandle);
				}
			}
			finally
			{
				handle.isClosed = true;
			}
		}
	}
}